//
//  OAuth2Handler.swift
//  SitPosition
//
//  Created by apple on 2019/1/20.
//  Copyright © 2019 apple. All rights reserved.
//

import Foundation
import Alamofire


class OAuth2Handler: RequestAdapter, RequestRetrier {
    private typealias RefreshCompletion = (_ succeeded: Bool, _ accessToken: AccessToken?) -> Void
    
    private let sessionManager: SessionManager = {
        let configuration = URLSessionConfiguration.default
        configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
        
        return SessionManager(configuration: configuration)
    }()
    
    private let lock = NSLock()
    private var baseURLString: String
    private let Authorization: String
    private let guestToken: String
    private var isRefreshing = false
    private var requestsToRetry: [RequestRetryCompletion] = []
    
    // 令牌更新回调(INJECTOR)
    public static var accessTokenSetter: (_ accessToken: AccessToken) -> Void = { _ in }
    public static var accessTokenGetter: () -> AccessToken? = { return nil }
    
    // MARK: - Initialization
    
    public init(baseURLString: String, Authorization: String, guestToken: String) {
        self.baseURLString = baseURLString
        self.Authorization = Authorization
        self.guestToken = guestToken
    }
    
    // MARK: - RequestAdapter
    
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        var urlRequest = urlRequest
        
        if let urlString = urlRequest.url?.absoluteString,
            urlString.hasPrefix(ServerURLString) {
            urlRequest.setValue(Authorization, forHTTPHeaderField: "clientInfo")
            
            if let accessToken = OAuth2Handler.accessTokenGetter() {
                let newUrlString = urlString.URLByAppendingQueryItems(
                    ["access_token" : accessToken.access_token]
                )
                urlRequest.url = URL(string: newUrlString)
            } else {
                let newUrlString = urlString.URLByAppendingQueryItems(
                    ["access_token" : guestToken]
                )
                urlRequest.url = URL(string: newUrlString)
            }
        }
        return urlRequest
    }
    
    // MARK: - RequestRetrier
    
    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
        lock.lock() ; defer { lock.unlock() }
        
        if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
            requestsToRetry.append(completion)
            
            if !isRefreshing {
                refreshTokens { [weak self] succeeded, accessToken in
                    guard let strongSelf = self else { return }
                    
                    strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }
                    
                    if let accessToken_ = accessToken {
                        OAuth2Handler.accessTokenSetter(accessToken_)
                    }
                    
                    strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) }
                    strongSelf.requestsToRetry.removeAll()
                }
            }
        } else {
            completion(false, 0.0)
        }
    }
    
    // MARK: - Private - Refresh Tokens
    
    private func refreshTokens(completion: @escaping RefreshCompletion) {
        guard !isRefreshing else { return }
        guard let accessToken = OAuth2Handler.accessTokenGetter(),
            let refreshToken = accessToken.refresh_token else {
            completion(false, nil)
            return
        }
        
        isRefreshing = true
        
        let urlString = "\(baseURLString)/oauth/token"
        
        let parameters: [String: String] = [
            "grant_type": "refresh_token",
            "refresh_token": refreshToken
        ]
        let api = urlString.URLByAppendingQueryItems(parameters)
        
        let headers: HTTPHeaders = [
            "Authorization": Authorization
        ]
        
        sessionManager.request(api, method: .post, parameters: nil, encoding: JSONEncoding.default, headers: headers)
            .responseJSON { [weak self] response in
                guard let strongSelf = self else { return }
                
                if let json = response.result.value as? [String: Any],
                    let accessToken = json["access_token"] as? String,
                    let refreshToken = json["refresh_token"] as? String,
                    let tokenType = json["token_type"] as? String {
                    let accessToken: AccessToken = AccessToken(
                        access_token: accessToken,
                        token_type: tokenType,
                        refresh_token: refreshToken,
                        expires_in: json["expires_in"] as? Int64,
                        scope: json["scope"] as? String
                    )
                    completion(true, accessToken)
                } else {
                    completion(false, nil)
                }
                
                strongSelf.isRefreshing = false
        }
    }
}
